﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HierarchicalClustering.Metrics;
using HierarchicalClustering.LinkageCriteria;
using System.Windows.Controls;

namespace HierarchicalClustering
{
    public class Clustering   // Implements the hierarchical clustering algorithm
    {
        private IMetric _metric;   // distance metric to use
        private ILinkage _linkage;   // linkage criteria to use
        public LinkedList<Cluster> Clusters { private set; get; }   // set of clusters
        private int _finalClusters;   // number of clusters to create
        private int _currentClusters;  // number of initial clusters
        private DistanceMatrix _matrix;   // distance matrix

        public Clustering ( string metric, string linkage, LinkedList<Cluster> clusters, int finalClusters, int numOfDocuments )
        {
            _metric = MetricFactory.GetMetric ( metric, numOfDocuments );  // get distance metric by name
            _linkage = LinkageFactory.GetLinkage(linkage);   // get linkage criteria by name
            Clusters = clusters;   // initial clusters
            _finalClusters = finalClusters;
            _currentClusters = clusters.Count;

            CreateDistanceMatrix();
        }

        private void CreateDistanceMatrix()
        {
            _matrix = new DistanceMatrix(Clusters.Count);
            LinkedListNode<Cluster> root = Clusters.First;
            LinkedListNode<Cluster> node;
            while (root != null)   // compute distances between every initial cluster
            {
                _matrix[root.Value.Number - 1, root.Value.Number - 1] = -1;
                node = root.Next;
                while (node != null)
                {
                    double value = _metric.Distance(root.Value.Documents.First.Value, node.Value.Documents.First.Value);
                    _matrix[root.Value.Number - 1, node.Value.Number - 1] = value;   // distance matrix is triangular
                    _matrix[node.Value.Number - 1, root.Value.Number - 1] = value;
                    node = node.Next;
                }
                root = root.Next;
            }
        }
      
        public void CreateClusters()   // run the hierarchical clustering algorithm
        {
            LinkedListNode<Cluster> root;
            LinkedListNode<Cluster> node;
            double min = -1, value;
            Cluster minCluster1 = null, minCluster2 = null;
            LinkedListNode<Cluster> minNode = null;

            for (int i = Clusters.Count; i > _finalClusters; i--)  // until we have "_finalClusters" clusters
            {
                min = -1;
                root = Clusters.First;
                while (root != null)  // for each cluster
                {
                    node = root.Next;
                    while (node != null)  // for each of its next clusters
                    {
                        value = _matrix[root.Value.Number - 1, node.Value.Number - 1];
                        if (value != -1)
                        {
                            value = _linkage.AddWeight(root.Value, node.Value, value);  // compute distance value
                            if (min == -1 || value < min)  // keep the pair of clusters with the minimum distance
                            {
                                min = value;
                                minCluster1 = root.Value;
                                minCluster2 = node.Value;
                                minNode = node;
                            }
                        }
                        node = node.Next;
                    }
                    root = root.Next;
                }
                minCluster1.Merge(minCluster2);   // merge that pair
                ComputeNewDistances(minCluster1, minCluster2);  // compute the new distances
                Clusters.Remove(minNode);
            }
        }

        private void ComputeNewDistances(Cluster minCluster1, Cluster minCluster2)
        {   // compute new distances from the new merged cluster
            double value;

            for (int i = 0; i < _currentClusters; i++)  // for each other cluster
            {
                if (i == minCluster1.Number - 1 || i == minCluster2.Number - 1)
                    continue;
                // compute the new distance
                value = _linkage.NewDistance(_matrix[minCluster1.Number - 1, i], _matrix[minCluster2.Number - 1, i]);
                _matrix[minCluster1.Number - 1, i] = value;   // and add it in the distance matrix
                _matrix[i, minCluster1.Number - 1] = value;
            }
        }

        //public void MergeRestClusters(IEnumerable<Cluster> restClusters)
        //{
        //    double? min = null;
        //    double value;
        //    Cluster minCluster = null;

        //    foreach (Cluster cluster in restClusters)
        //    {
        //        min = null;
        //        foreach (Cluster newCluster in Clusters)
        //        {
        //            value = _linkage.Distance(cluster, newCluster, _metric);
        //            value = _linkage.AddWeight(cluster, newCluster, value);
        //            if (min == null || value < min)
        //            {
        //                min = value;
        //                minCluster = newCluster;
        //            }
        //        }
        //        minCluster.Merge(cluster);
        //    }
        //}
    }
}
